/*
 * Copyright (C) 2012-2013  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.sample.network.swing;

import java.awt.CardLayout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import javax.naming.ServiceUnavailableException;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.text.JTextComponent;

import nemos.remote.rsa.message.IConnection;
import nemos.remote.socket.ISocketService;
import nemos.swing.GenerationException;
import nemos.swing.IComponentFactory;
import nemos.swing.util.BackgroundAction;
import nemos.swing.util.ResourceComponentFactory;
import nemos.util.log.NemosLogger;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;

/**
 * ConnectionPanelFactory
 * 
 */
@Property(name = IComponentFactory.COMPONENT_TYPE, value = "nemos.sample.network.connection")
@Reference(name = "connection", referenceInterface = IConnection.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@Component
public class ConnectionPanelFactory extends ResourceComponentFactory<JComponent> {

    /** The connection panel resource file name */
    private static final String CONNECTION_PANEL = "connectionPanel"; //$NON-NLS-1$

    /** The logger */
    final NemosLogger mLogger;

    /** The current connection */
    final AtomicReference<IConnection> mConnection;

    /** The connection panels */
    private final Collection<ConnectionPanel> mConnectionPanels;

    /** The socket service */
    @Reference(name = "socketService", cardinality = ReferenceCardinality.MANDATORY_UNARY)
    private ISocketService mSocketService;

    /**
     * Constructor.
     */
    public ConnectionPanelFactory() {
        super(JComponent.class);

        mLogger = new NemosLogger(ConnectionPanelFactory.class);
        mConnection = new AtomicReference<IConnection>();
        mConnectionPanels = Collections.synchronizedCollection(new ArrayList<ConnectionPanel>());
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.swing.util.ResourceComponentFactory#createComponent(java.util.Map)
     */
    @Override
    public JComponent createComponent(final Map<String, ?> pInitParams) {
        try {
            loadComponents(pInitParams);

            // Collect components
            final JTextField lHost = getComponent("host", JTextField.class); //$NON-NLS-1$
            final JTextField lPort = getComponent("port", JTextField.class); //$NON-NLS-1$
            final AbstractButton lServerButton = getComponent("server", AbstractButton.class); //$NON-NLS-1$
            final JButton lCreateButton = getComponent("connect", JButton.class); //$NON-NLS-1$
            final JTextComponent lDesc = getComponent("description", JTextComponent.class); //$NON-NLS-1$
            final JButton lDisconnectButton = getComponent("disconnect", JButton.class); //$NON-NLS-1$
            final JComponent lPanel = getComponent("panel", JComponent.class); //$NON-NLS-1$

            // Configure actions
            lCreateButton.setAction(new BackgroundAction<Void, Void>(lCreateButton) {
                /** Serial version */
                private static final long serialVersionUID = 1L;

                @Override
                protected Void doInBackground() throws Exception {
                    ConnectionPanelFactory.this.tryCreate(lHost.getText(), lPort.getText(),
                            lServerButton.isSelected());
                    return null;
                }

                /**
                 * {@inheritDoc}
                 * 
                 * @see nemos.swing.util.BackgroundAction#handleException(java.lang.Throwable)
                 */
                @Override
                protected void handleException(final Throwable pException) {
                    mLogger.error(NetworkMessage.CONNECT_ERROR, pException);

                    final String lTitle = mLogger.getTranslator().getLabel(
                            NetworkMessage.CONNECT_ERROR_FRAME_TITLE);
                    final String lMsg = mLogger.getTranslator().getLabel(
                            NetworkMessage.CONNECT_ERROR_FRAME_MSG, pException.getMessage());
                    JOptionPane.showMessageDialog(lPanel.getRootPane(), lMsg, lTitle,
                            JOptionPane.ERROR_MESSAGE);
                }
            });
            lDisconnectButton.setAction(new BackgroundAction<Void, Void>(lDisconnectButton) {
                /** Serial version */
                private static final long serialVersionUID = 1L;

                @Override
                protected Void doInBackground() {
                    final IConnection lConnection = mConnection.get();
                    if (lConnection != null) {
                        lConnection.stop();
                        unbindConnection(lConnection);
                    }
                    return null;
                }
            });

            mConnectionPanels.add(new ConnectionPanel(lPanel, lDesc));

            // Enclosing component
            return lPanel;
        } catch (final GenerationException e) {
            return createDefault(e, null);
        }
    }

    /**
     * Try to create a connection.
     * 
     * @param pHost
     *            The server host name
     * @param pPort
     *            The server port number
     * @param pServer
     *            The server creation flag
     * @throws IOException
     *             If an I/O error occurred
     * @throws ServiceUnavailableException
     *             If socket service is no longer available
     */
    protected void tryCreate(final String pHost, final String pPort, final boolean pServer)
            throws IOException, ServiceUnavailableException {
        // Convert port value
        final int lPort = Integer.parseInt(pPort.trim());

        // Check service availability
        final ISocketService lService = mSocketService;
        if (lService == null) {
            throw new ServiceUnavailableException();
        }

        // Try to create or join server
        IConnection lConnection;
        if (pServer) {
            lConnection = lService.createServer(pHost.trim(), lPort);
        } else {
            lConnection = lService.connectServer(pHost, lPort);
        }
        synchronized (mConnectionPanels) {
            mConnection.set(lConnection);
            for (final ConnectionPanel lPanel : mConnectionPanels) {
                lPanel.displayConnection(lConnection);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.swing.util.ResourceComponentFactory#getResourceName(java.util.Map)
     */
    @Override
    protected String getResourceName(@SuppressWarnings("unused") final Map<String, ?> pInitParams) {
        return CONNECTION_PANEL;
    }

    /**
     * Bind the socketService.
     * 
     * @param pSocketService
     *            The socket service to bind
     */
    public void bindSocketService(final ISocketService pSocketService) {
        mSocketService = pSocketService;
    }

    /**
     * Unbind the socketService.
     * 
     * @param pSocketService
     *            The socket service to unbind
     */
    public void unbindSocketService(@SuppressWarnings("unused") final ISocketService pSocketService) {
        mSocketService = null;
    }

    /**
     * Bind the connection.
     * 
     * @param pConnection
     *            The connection to bind
     */
    @SuppressWarnings("unused")
    public void bindConnection(final IConnection pConnection) {
        // Ignore
    }

    /**
     * Unbind the connection.
     * 
     * @param pConnection
     *            The connection to unbind
     */
    public void unbindConnection(final IConnection pConnection) {

        final IConnection lConnection = mConnection.get();
        if (lConnection != null && lConnection.getId().equals(pConnection.getId())
                && mConnection.compareAndSet(lConnection, null)) {

            synchronized (mConnectionPanels) {
                for (final ConnectionPanel lPanel : mConnectionPanels) {
                    lPanel.hideConnection();
                }
            }
        }
    }

    /**
     * ConnectionPanel : The connection panel class.
     */
    private static class ConnectionPanel {

        /** The online panel name */
        private static final String ONLINE_PANEL_NAME = "online"; //$NON-NLS-1$

        /** The offline panel name */
        private static final String OFFLINE_PANEL_NAME = "offline"; //$NON-NLS-1$

        /** The connections's description label */
        private final JTextComponent mConnectionDescription;

        /** The main panel */
        private final JComponent mPanel;

        /**
         * Constructor
         * 
         * @param pPanel
         *            The main panel
         * @param pDesc
         *            The description label
         */
        public ConnectionPanel(final JComponent pPanel, final JTextComponent pDesc) {
            super();
            mPanel = pPanel;
            mConnectionDescription = pDesc;
        }

        /**
         * Display a connection.
         * 
         * @param pConnection
         *            The connection to display
         */
        public void displayConnection(final IConnection pConnection) {
            mConnectionDescription.setText(pConnection.getId());
            display(ONLINE_PANEL_NAME);
        }

        /**
         * Hide the current connection.
         */
        public void hideConnection() {
            display(OFFLINE_PANEL_NAME);
        }

        /**
         * Display the required card panel.
         * 
         * @param pName
         *            The card panel name
         */
        private void display(final String pName) {
            ((CardLayout) mPanel.getLayout()).show(mPanel, pName);
        }
    }
}
